#include<iostream>
#include<pthread.h>
#include<time.h>
#include<queue>
#include<unistd.h>
#include<string>
using namespace std;

const int gcap=5;

// 核心思想: 
// 队列为空，消费者不应该再消费, 要等待, 后面有数据还要唤醒
// 队列为满，生产者不应该再生产, 要等待, 后面有数据还要唤醒
// 所以两者都要有自己的条件变量


// 不要认为, 阻塞队列只能放整数字符串之类的, 也可以放对象
template<class T>
class BlockQueue
{
public:
    BlockQueue(const int cap=gcap)
        :_cap(cap)
    {
        pthread_mutex_init(&mutex,nullptr);
        pthread_cond_init(&_customerCond,nullptr);
        pthread_cond_init(&_producerCond,nullptr);
    }

    bool isFull()
    {
        return _q.size()==_cap;
    }

    bool isEmpty()
    {
        return _q.empty();
    }

    void push(const T&in)
    {
        pthread_mutex_lock(&mutex);
        // 细节1: 一定要保证, 在任何时候, 都是符合条件, 才进行生产
        while(isFull())  //1. 我们只能在临界区内部, 判断临界资源是否就绪! 注定了我们在当前一定是持有锁的!
        {
            //2. 要让线程休眠等待, 不能持有锁等待!
            //3. 注定了, pthread_cond_wait要有锁的释放的能力!
            pthread_cond_wait(&_producerCond,&mutex);  //我休眠(切换)了, 我醒来的时候, 在哪里往后执行呢?
            //4. 当线程醒来的时候, 注定了继续从临界区内部继续运行! 因为我是在临界区被切走的!
            //5. 注定了当线程被唤醒的时候, 继续在pthread_cond_wait函数处向后运行, 又要重新申请锁, 申请成功才会彻底返回
        }
        // 没有满的, 要让他进行生产
        _q.push(in);
        // 加策略
        // if(_q.size() >= _cap/2) 
        pthread_cond_signal(&_customerCond);           //唤醒, 要去唤醒对方
        pthread_mutex_unlock(&mutex);
        //pthread_cond_signal(&_customerCond);         //也可以放在后面
    }


    void pop(T*out)
    {
        pthread_mutex_lock(&mutex);
        while(isEmpty())
        {
            pthread_cond_wait(&_customerCond,&mutex);   //等待
        }
        *out=_q.front();
        _q.pop();
        pthread_cond_signal(&_producerCond);           //唤醒
        pthread_mutex_unlock(&mutex);
    }


    ~BlockQueue()
    {
        pthread_mutex_destroy(&mutex);
        pthread_cond_destroy(&_customerCond);
        pthread_cond_destroy(&_producerCond);
    }

private:
    queue<T> _q;
    int _cap;                       //队列容量上限, 队列空满两种情况都要考虑
    // 为什么这份代码, 只用一把锁呢?根本原因在于, 
    // 我们生产和消费访问的是同一个queue && queue被当做整体使用
    pthread_mutex_t mutex;          //保证数据安全
    pthread_cond_t _customerCond;   //消费者对应的条件变量, 空, wait 
    pthread_cond_t _producerCond;   //生产者对应的条件变量, 满, wait

};
void*customer(void*args)
{
    BlockQueue<int> *bq=static_cast<BlockQueue<int>*>(args);
    while(true)
    {
        //现象: 开始消费者,消费的慢, 生产者一瞬间把阻塞队列全部打满, 后面消费一个, 生产一个
        // sleep(1);
        // 1. 将数据从blockqueue中获取 --- 获取到了数据
        int data=0;
        bq->pop(&data);
        // 2. 结合某种业务逻辑处理数据 --- TODO
        cout<<"customer data: "<<data<<endl;
    }
}

void*producer(void*args)
{
    BlockQueue<int> *bq=static_cast<BlockQueue<int>*>(args);
    while(true)
    {
        sleep(1);       //现象: 生产一个, 消费一个
        // 1. 先通过某种渠道获取数据
        int data=rand()%10+1;
        // 2. 将数据推送到blockqueue中 --- 完成生产过程
        bq->push(data);
        cout<<"producer data: "<<data<<endl;
    }
}

int main()
{
    // 单生产和单消费  --->  多生产和多消费
    srand((uint64_t)time(nullptr)^getpid());
    BlockQueue<int> *bq=new BlockQueue<int>();
    pthread_t c,p;

    // 让消费者和生产者看到同一个阻塞队列
    pthread_create(&c,nullptr,customer,bq);
    pthread_create(&p,nullptr,producer,bq);

    pthread_join(c,nullptr);
    pthread_join(p,nullptr);

    delete bq;
    return 0;
}
